import { isObject } from "../utils";
import { arrayMethods } from "./array";
import Dep from "./dep";

class Observer {
  constructor(data) {
    this.dep = new Dep(); // 数据可能是数组或者对象
    // 如果给对象新增一个属性不会触发视图更新 $set  => 给对象本身也增加一个watcher 如果增加一个属性后，我就手动触发watcehr的更新
    // 数组需要 通过__ob__引用拿到observeArray方法
    // data.__ob__ = this;
    // 不可枚举，不能被循环出来
    Object.defineProperty(data, "__ob__", {
      enumerable: false,
      value: this,
    });
    if (Array.isArray(data)) {
      // 数组劫持
      // 对数组原来的方法进行改写，切片编程  高阶函数
      data.__proto__ = arrayMethods;
      // 如果数组中的数据是对象类型，需要监听对象变化
      this.observeArray(data);
    } else {
      // 对象劫持
      this.walk(data);
    }
  }
  // 对数组的数组和数组的对象再次进行劫持 ，递归
  observeArray(data) {
    // 如果数组里放的是对象类型，也做了观测 也做了依赖收集
    data.forEach((item) => {
      observe(item);
    });
  }
  // 对象
  walk(data) {
    Object.keys(data).forEach((key) => {
      defineReactive(data, key, data[key]);
    });
  }
}

function dependArray(value) {
  for (let i = 0; i < value.length; i++) {
    let current = value[i];
    // current 数组里面的数组
    current.__ob__ && current.__ob__.dep.depend();
    if (Array.isArray(current)) {
      dependArray(current);
    }
  }
}

// vue2 会对对象进行遍历，将每个属性用defineProperty重新定义，性能差
function defineReactive(data, key, value) {
  // value有可能是对象
  let childOb = observe(value); // 本身用户默认值是对象套对象，需要递归处理，本身性能也不高
  let dep = new Dep(); // 每个属性都有一个dep属性
  Object.defineProperty(data, key, {
    get() {
      // 取值时 希望将watcher和dep对应起来
      if (Dep.target) {
        // 此时是在模板中取值的
        // 让dep记住watcher
        dep.depend();
        if (childOb) {
          // 数组或者对象 后续$set时 需要触发它自己的更新  外层逻辑收集依赖
          childOb.dep.depend(); // 让数组和对象也记录watcher
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value;
    },
    set(newVal) {
      if (newVal !== value) {
        // 如果用户赋值一个新对象，需要将这个 对象进行劫持
        observe(newVal);
        value = newVal;
        dep.notify();
      }
    },
  });
}

export function observe(data) {
  // 如果是对象才观察
  if (!isObject(data)) {
    return;
  }
  if (data.__ob__) {
    return data.__ob__;
  }
  return new Observer(data);
}

// 每个属性 需要依赖收集，需要知道哪些页面有关联
// 每个属性建立dep对象 取值时候。dep.depend() 进行依赖收集。
// dep.depend() => Watcher.addDep(dep) 渲染watcher添加依赖项
// dep.addSub(watcher) 依赖项添加观察者
// 值改变时。dep通知关联页面 dep.notify() watcher => watcher.update()
